// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/leveldb_wrapper_impl.h"

#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "components/leveldb/public/cpp/util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/mock_leveldb_database.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "testing/gtest/include/gtest/gtest.h"

using leveldb::StdStringToUint8Vector;
using leveldb::Uint8VectorToStdString;

namespace content {

namespace {

    const char* kTestPrefix = "abc";
    const char* kTestSource = "source";
    const size_t kTestSizeLimit = 512;

    class GetAllCallback : public mojom::LevelDBWrapperGetAllCallback {
    public:
        static mojom::LevelDBWrapperGetAllCallbackAssociatedPtrInfo CreateAndBind(
            mojo::AssociatedGroup* associated_group,
            bool* result,
            const base::Closure& callback)
        {
            mojom::LevelDBWrapperGetAllCallbackAssociatedPtrInfo ptr_info;
            mojom::LevelDBWrapperGetAllCallbackAssociatedRequest request;
            associated_group->CreateAssociatedInterface(
                mojo::AssociatedGroup::WILL_PASS_PTR, &ptr_info, &request);
            mojo::MakeStrongAssociatedBinding(
                base::WrapUnique(new GetAllCallback(result, callback)),
                std::move(request));
            return ptr_info;
        }

    private:
        GetAllCallback(bool* result, const base::Closure& callback)
            : m_result(result)
            , m_callback(callback)
        {
        }
        void Complete(bool success) override
        {
            *m_result = success;
            m_callback.Run();
        }

        bool* m_result;
        base::Closure m_callback;
    };

    class MockDelegate : public LevelDBWrapperImpl::Delegate {
    public:
        void OnNoBindings() override { }
        std::vector<leveldb::mojom::BatchedOperationPtr> PrepareToCommit() override
        {
            return std::vector<leveldb::mojom::BatchedOperationPtr>();
        }
        void DidCommit(leveldb::mojom::DatabaseError error) override { }
    };

    void GetCallback(const base::Closure& callback,
        bool* success_out,
        std::vector<uint8_t>* value_out,
        bool success,
        const std::vector<uint8_t>& value)
    {
        *success_out = success;
        *value_out = value;
        callback.Run();
    }

    void SuccessCallback(const base::Closure& callback,
        bool* success_out,
        bool success)
    {
        *success_out = success;
        callback.Run();
    }

    void NoOpSuccessCallback(bool success) { }

} // namespace

class LevelDBWrapperImplTest : public testing::Test,
                               public mojom::LevelDBObserver {
public:
    struct Observation {
        enum { kAdd,
            kChange,
            kDelete,
            kDeleteAll } type;
        std::string key;
        std::string old_value;
        std::string new_value;
        std::string source;
    };

    LevelDBWrapperImplTest()
        : db_(&mock_data_)
        , level_db_wrapper_(&db_,
              kTestPrefix,
              kTestSizeLimit,
              base::TimeDelta::FromSeconds(5),
              10 * 1024 * 1024 /* max_bytes_per_hour */,
              60 /* max_commits_per_hour */,
              &delegate_)
        , observer_binding_(this)
    {
        set_mock_data(std::string(kTestPrefix) + "def", "defdata");
        set_mock_data(std::string(kTestPrefix) + "123", "123data");
        set_mock_data("123", "baddata");

        level_db_wrapper_.Bind(mojo::MakeRequest(&level_db_wrapper_ptr_));
        mojom::LevelDBObserverAssociatedPtrInfo ptr_info;
        observer_binding_.Bind(&ptr_info, associated_group());
        level_db_wrapper_ptr_->AddObserver(std::move(ptr_info));
    }

    void set_mock_data(const std::string& key, const std::string& value)
    {
        mock_data_[StdStringToUint8Vector(key)] = StdStringToUint8Vector(value);
    }

    void set_mock_data(const std::vector<uint8_t>& key,
        const std::vector<uint8_t>& value)
    {
        mock_data_[key] = value;
    }

    bool has_mock_data(const std::string& key)
    {
        return mock_data_.find(StdStringToUint8Vector(key)) != mock_data_.end();
    }

    std::string get_mock_data(const std::string& key)
    {
        return has_mock_data(key)
            ? Uint8VectorToStdString(mock_data_[StdStringToUint8Vector(key)])
            : "";
    }

    mojom::LevelDBWrapper* wrapper() { return level_db_wrapper_ptr_.get(); }
    mojo::AssociatedGroup* associated_group()
    {
        return level_db_wrapper_ptr_.associated_group();
    }

    bool GetSync(const std::vector<uint8_t>& key, std::vector<uint8_t>* result)
    {
        base::RunLoop run_loop;
        bool success = false;
        wrapper()->Get(key, base::Bind(&GetCallback, run_loop.QuitClosure(), &success, result));
        run_loop.Run();
        return success;
    }

    bool PutSync(const std::vector<uint8_t>& key,
        const std::vector<uint8_t>& value,
        std::string source = kTestSource)
    {
        base::RunLoop run_loop;
        bool success = false;
        wrapper()->Put(
            key, value, source,
            base::Bind(&SuccessCallback, run_loop.QuitClosure(), &success));
        run_loop.Run();
        return success;
    }

    bool DeleteSync(const std::vector<uint8_t>& key)
    {
        base::RunLoop run_loop;
        bool success = false;
        wrapper()->Delete(
            key, kTestSource,
            base::Bind(&SuccessCallback, run_loop.QuitClosure(), &success));
        run_loop.Run();
        return success;
    }

    bool DeleteAllSync()
    {
        base::RunLoop run_loop;
        bool success = false;
        wrapper()->DeleteAll(
            kTestSource,
            base::Bind(&SuccessCallback, run_loop.QuitClosure(), &success));
        run_loop.Run();
        return success;
    }

    void CommitChanges() { level_db_wrapper_.ScheduleImmediateCommit(); }

    const std::vector<Observation>& observations() { return observations_; }

private:
    // LevelDBObserver:
    void KeyAdded(const std::vector<uint8_t>& key,
        const std::vector<uint8_t>& value,
        const std::string& source) override
    {
        observations_.push_back({ Observation::kAdd, Uint8VectorToStdString(key), "",
            Uint8VectorToStdString(value), source });
    }
    void KeyChanged(const std::vector<uint8_t>& key,
        const std::vector<uint8_t>& new_value,
        const std::vector<uint8_t>& old_value,
        const std::string& source) override
    {
        observations_.push_back({ Observation::kChange, Uint8VectorToStdString(key),
            Uint8VectorToStdString(old_value),
            Uint8VectorToStdString(new_value), source });
    }
    void KeyDeleted(const std::vector<uint8_t>& key,
        const std::vector<uint8_t>& old_value,
        const std::string& source) override
    {
        observations_.push_back({ Observation::kDelete, Uint8VectorToStdString(key),
            Uint8VectorToStdString(old_value), "", source });
    }
    void AllDeleted(const std::string& source) override
    {
        observations_.push_back({ Observation::kDeleteAll, "", "", "", source });
    }

    TestBrowserThreadBundle thread_bundle_;
    std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_;
    MockLevelDBDatabase db_;
    MockDelegate delegate_;
    LevelDBWrapperImpl level_db_wrapper_;
    mojom::LevelDBWrapperPtr level_db_wrapper_ptr_;
    mojo::AssociatedBinding<mojom::LevelDBObserver> observer_binding_;
    std::vector<Observation> observations_;
};

TEST_F(LevelDBWrapperImplTest, GetLoadedFromMap)
{
    std::vector<uint8_t> result;
    EXPECT_TRUE(GetSync(StdStringToUint8Vector("123"), &result));
    EXPECT_EQ(StdStringToUint8Vector("123data"), result);

    EXPECT_FALSE(GetSync(StdStringToUint8Vector("x"), &result));
}

TEST_F(LevelDBWrapperImplTest, GetFromPutOverwrite)
{
    std::vector<uint8_t> key = StdStringToUint8Vector("123");
    std::vector<uint8_t> value = StdStringToUint8Vector("foo");

    EXPECT_TRUE(PutSync(key, value));

    std::vector<uint8_t> result;
    EXPECT_TRUE(GetSync(key, &result));
    EXPECT_EQ(value, result);
}

TEST_F(LevelDBWrapperImplTest, GetFromPutNewKey)
{
    std::vector<uint8_t> key = StdStringToUint8Vector("newkey");
    std::vector<uint8_t> value = StdStringToUint8Vector("foo");

    EXPECT_TRUE(PutSync(key, value));

    std::vector<uint8_t> result;
    EXPECT_TRUE(GetSync(key, &result));
    EXPECT_EQ(value, result);
}

TEST_F(LevelDBWrapperImplTest, GetAll)
{
    leveldb::mojom::DatabaseError status;
    std::vector<mojom::KeyValuePtr> data;
    base::RunLoop run_loop;
    bool result = false;
    EXPECT_TRUE(wrapper()->GetAll(
        GetAllCallback::CreateAndBind(associated_group(), &result,
            run_loop.QuitClosure()),
        &status, &data));
    EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
    EXPECT_EQ(2u, data.size());
    EXPECT_FALSE(result);
    run_loop.Run();
    EXPECT_TRUE(result);
}

TEST_F(LevelDBWrapperImplTest, CommitPutToDB)
{
    std::string key1 = "123";
    std::string value1 = "foo";
    std::string key2 = "abc";
    std::string value2 = "data abc";

    EXPECT_TRUE(
        PutSync(StdStringToUint8Vector(key1), StdStringToUint8Vector(value1)));
    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key2),
        StdStringToUint8Vector("old value")));
    EXPECT_TRUE(
        PutSync(StdStringToUint8Vector(key2), StdStringToUint8Vector(value2)));

    EXPECT_FALSE(has_mock_data(kTestPrefix + key2));

    CommitChanges();
    EXPECT_TRUE(has_mock_data(kTestPrefix + key1));
    EXPECT_EQ(value1, get_mock_data(kTestPrefix + key1));
    EXPECT_TRUE(has_mock_data(kTestPrefix + key2));
    EXPECT_EQ(value2, get_mock_data(kTestPrefix + key2));
}

TEST_F(LevelDBWrapperImplTest, PutObservations)
{
    std::string key = "new_key";
    std::string value1 = "foo";
    std::string value2 = "data abc";
    std::string source1 = "source1";
    std::string source2 = "source2";

    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key),
        StdStringToUint8Vector(value1), source1));
    ASSERT_EQ(1u, observations().size());
    EXPECT_EQ(Observation::kAdd, observations()[0].type);
    EXPECT_EQ(key, observations()[0].key);
    EXPECT_EQ(value1, observations()[0].new_value);
    EXPECT_EQ(source1, observations()[0].source);

    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key),
        StdStringToUint8Vector(value2), source2));
    ASSERT_EQ(2u, observations().size());
    EXPECT_EQ(Observation::kChange, observations()[1].type);
    EXPECT_EQ(key, observations()[1].key);
    EXPECT_EQ(value1, observations()[1].old_value);
    EXPECT_EQ(value2, observations()[1].new_value);
    EXPECT_EQ(source2, observations()[1].source);
}

TEST_F(LevelDBWrapperImplTest, DeleteNonExistingKey)
{
    EXPECT_TRUE(DeleteSync(StdStringToUint8Vector("doesn't exist")));
    EXPECT_EQ(0u, observations().size());
}

TEST_F(LevelDBWrapperImplTest, DeleteExistingKey)
{
    std::string key = "newkey";
    std::string value = "foo";
    set_mock_data(kTestPrefix + key, value);

    EXPECT_TRUE(DeleteSync(StdStringToUint8Vector(key)));
    ASSERT_EQ(1u, observations().size());
    EXPECT_EQ(Observation::kDelete, observations()[0].type);
    EXPECT_EQ(key, observations()[0].key);
    EXPECT_EQ(value, observations()[0].old_value);
    EXPECT_EQ(kTestSource, observations()[0].source);

    EXPECT_TRUE(has_mock_data(kTestPrefix + key));

    CommitChanges();
    EXPECT_FALSE(has_mock_data(kTestPrefix + key));
}

TEST_F(LevelDBWrapperImplTest, DeleteAllWithoutLoadedMap)
{
    std::string key = "newkey";
    std::string value = "foo";
    std::string dummy_key = "foobar";
    set_mock_data(dummy_key, value);
    set_mock_data(kTestPrefix + key, value);

    EXPECT_TRUE(DeleteAllSync());
    ASSERT_EQ(1u, observations().size());
    EXPECT_EQ(Observation::kDeleteAll, observations()[0].type);
    EXPECT_EQ(kTestSource, observations()[0].source);

    EXPECT_TRUE(has_mock_data(kTestPrefix + key));
    EXPECT_TRUE(has_mock_data(dummy_key));

    CommitChanges();
    EXPECT_FALSE(has_mock_data(kTestPrefix + key));
    EXPECT_TRUE(has_mock_data(dummy_key));

    // Deleting all again should still work, an cause an observation.
    EXPECT_TRUE(DeleteAllSync());
    ASSERT_EQ(2u, observations().size());
    EXPECT_EQ(Observation::kDeleteAll, observations()[1].type);
    EXPECT_EQ(kTestSource, observations()[1].source);

    // And now we've deleted all, writing something the quota size should work.
    EXPECT_TRUE(PutSync(std::vector<uint8_t>(kTestSizeLimit, 'b'),
        std::vector<uint8_t>()));
}

TEST_F(LevelDBWrapperImplTest, DeleteAllWithLoadedMap)
{
    std::string key = "newkey";
    std::string value = "foo";
    std::string dummy_key = "foobar";
    set_mock_data(dummy_key, value);

    EXPECT_TRUE(
        PutSync(StdStringToUint8Vector(key), StdStringToUint8Vector(value)));

    EXPECT_TRUE(DeleteAllSync());
    ASSERT_EQ(2u, observations().size());
    EXPECT_EQ(Observation::kDeleteAll, observations()[1].type);
    EXPECT_EQ(kTestSource, observations()[1].source);

    EXPECT_TRUE(has_mock_data(dummy_key));

    CommitChanges();
    EXPECT_FALSE(has_mock_data(kTestPrefix + key));
    EXPECT_TRUE(has_mock_data(dummy_key));
}

TEST_F(LevelDBWrapperImplTest, DeleteAllWithPendingMapLoad)
{
    std::string key = "newkey";
    std::string value = "foo";
    std::string dummy_key = "foobar";
    set_mock_data(dummy_key, value);

    wrapper()->Put(StdStringToUint8Vector(key), StdStringToUint8Vector(value),
        kTestSource, base::Bind(&NoOpSuccessCallback));

    EXPECT_TRUE(DeleteAllSync());
    ASSERT_EQ(2u, observations().size());
    EXPECT_EQ(Observation::kDeleteAll, observations()[1].type);
    EXPECT_EQ(kTestSource, observations()[1].source);

    EXPECT_TRUE(has_mock_data(dummy_key));

    CommitChanges();
    EXPECT_FALSE(has_mock_data(kTestPrefix + key));
    EXPECT_TRUE(has_mock_data(dummy_key));
}

TEST_F(LevelDBWrapperImplTest, PutOverQuotaLargeValue)
{
    std::vector<uint8_t> key = StdStringToUint8Vector("newkey");
    std::vector<uint8_t> value(kTestSizeLimit, 4);

    EXPECT_FALSE(PutSync(key, value));

    value.resize(kTestSizeLimit / 2);
    EXPECT_TRUE(PutSync(key, value));
}

TEST_F(LevelDBWrapperImplTest, PutOverQuotaLargeKey)
{
    std::vector<uint8_t> key(kTestSizeLimit, 'a');
    std::vector<uint8_t> value = StdStringToUint8Vector("newvalue");

    EXPECT_FALSE(PutSync(key, value));

    key.resize(kTestSizeLimit / 2);
    EXPECT_TRUE(PutSync(key, value));
}

TEST_F(LevelDBWrapperImplTest, PutWhenAlreadyOverQuota)
{
    std::string key = "largedata";
    std::vector<uint8_t> value(kTestSizeLimit, 4);

    set_mock_data(kTestPrefix + key, Uint8VectorToStdString(value));

    // Put with same data should succeed.
    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value));

    // Put with same data size should succeed.
    value[1] = 13;
    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value));

    // Adding a new key when already over quota should not succeed.
    EXPECT_FALSE(PutSync(StdStringToUint8Vector("newkey"), { 1, 2, 3 }));

    // Reducing size should also succeed.
    value.resize(kTestSizeLimit / 2);
    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value));

    // Increasing size again should succeed, as still under the limit.
    value.resize(value.size() + 1);
    EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value));

    // But increasing back to original size should fail.
    value.resize(kTestSizeLimit);
    EXPECT_FALSE(PutSync(StdStringToUint8Vector(key), value));
}

TEST_F(LevelDBWrapperImplTest, PutWhenAlreadyOverQuotaBecauseOfLargeKey)
{
    std::vector<uint8_t> key(kTestSizeLimit, 'x');
    std::vector<uint8_t> value = StdStringToUint8Vector("value");

    set_mock_data(kTestPrefix + Uint8VectorToStdString(key),
        Uint8VectorToStdString(value));

    // Put with same data size should succeed.
    value[0] = 'X';
    EXPECT_TRUE(PutSync(key, value));

    // Reducing size should also succeed.
    value.clear();
    EXPECT_TRUE(PutSync(key, value));

    // Increasing size should fail.
    value.resize(1, 'a');
    EXPECT_FALSE(PutSync(key, value));
}

} // namespace content
